package com.lmaye.eshop.inventory.controller;


import com.lmaye.eshop.inventory.common.Response;
import com.lmaye.eshop.inventory.entity.ProductInventory;
import com.lmaye.eshop.inventory.request.ProductInventoryCacheRefreshRequest;
import com.lmaye.eshop.inventory.request.ProductInventoryDbUpdateRequest;
import com.lmaye.eshop.inventory.request.Request;
import com.lmaye.eshop.inventory.service.ProductInventoryService;
import com.lmaye.eshop.inventory.service.RequestAsyncProcessService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Objects;

/**
 * <p>
 * 商品库存 前端控制器
 * </p>
 *
 * @author Lottery
 * @since 2019-03-19
 */
@Slf4j
@RestController
@RequestMapping("/productInventory")
public class ProductInventoryController {
    /**
     * RequestAsyncProcessService 请求异步处理Service
     */
    @Autowired
    private RequestAsyncProcessService requestAsyncProcessService;

    /**
     * ProductInventoryService 商品库存 服务类
     */
    @Autowired
    private ProductInventoryService productInventoryService;

    /**
     * 更新商品库存
     *
     * @param productInventory  商品库存
     * @return  Response
     */
    @RequestMapping("/updateProductInventory")
    @ResponseBody
    public Response updateProductInventory(ProductInventory productInventory) {
        log.info("************** 演示: 更新商品库存 [/updateProductInventory] 请求 - 商品id [{}], 商品库存 [{}]", productInventory.getProductId(), productInventory.getInventoryCount());
        Request request = new ProductInventoryDbUpdateRequest(productInventory, productInventoryService);
        requestAsyncProcessService.process(request);
        return Response.success("商品库存 - 更新成功");
    }

    /**
     * 查询商品库存
     *
     * @param productId 商品ID
     * @return  Response<ProductInventory>
     */
    @GetMapping("/query/{productId}")
    @ResponseBody
    public Response<ProductInventory> queryProductInventory(@PathVariable Integer productId) {
        log.info("************** 演示: 查询商品库存 [/query/] 请求 - 商品id [{}]", productId);
        ProductInventory productInventory = null;
        try {
            Request request = new ProductInventoryCacheRefreshRequest(productId, productInventoryService, false);
            requestAsyncProcessService.process(request);
            /**
             * 将请求扔给service异步去处理以后，就需要while(true)一会，在这里等待
             * 去尝试等待前面有商品库存更新的操作，同时缓存刷新的操作，将最新的数据刷新到缓存中
             */
            long startTime = System.currentTimeMillis();
            long waitTime = 0L;
            while(true) {
                /*// TODO 模拟演示：便于查看效果（25s后再处理）
                if(waitTime > 25000) {
                    break;
                }*/
                // 等待超过200ms没有从缓存中获取到结果，就尝试从数据库中获取
                if(waitTime > 200) {
                    break;
                }
                // 尝试去redis中读取一次商品库存的缓存数据
                productInventory = productInventoryService.getProductInventoryCache(productId);
                if(Objects.nonNull(productInventory)) {
                    log.info("************** 演示: 在200ms内读取到redis中的库存缓存 - 商品id [{}], 商品库存 [{}]", productInventory.getProductId(), productInventory.getInventoryCount());
                    return Response.success(productInventory);
                } else {
                    // 如果redis缓存中没有读取到结果，那么就等待一段时间
                    Thread.sleep(20);
                    waitTime = startTime - System.currentTimeMillis();
                }
            }
            // 直接尝试从数据库中读取数据
            productInventory = productInventoryService.getById(productId);
            if(Objects.nonNull(productInventory)) {
                /**
                 * 将缓存刷新一下
                 * 这个过程，实际上是一个读操作的过程，所以应该放到队列中串行处理，防止数据不一致的问题
                 *
                 * 代码运行到这里，只有三种情况：
                 * 1. 上一次也是读请求，数据刷入了redis，但是redis LRU算法给清理掉了，标志位还是false
                 * 所以此时下一个读请求是从缓存中拿不到数据的，再放一个读Request进队列，让数据刷新一下
                 * 2. 可能在200ms内，就是读请求在队列中一直积压着，没有等待到它执行
                 * 所以就直接查一次数据库，然后给队列里塞进去一个刷新缓存的请求
                 * 3. 数据库本身就没有，缓存穿透，穿透redis，请求到达msql数据库
                 */
                request = new ProductInventoryCacheRefreshRequest(productId, productInventoryService, true);
                requestAsyncProcessService.process(request);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Response.success(productInventory);
    }
}
